//
//  CouchFS.m
//  CouchFS
//
//  Created by Tim Kofol on 12/7/07.
//  Copyright 2007 HighImpactNoise.com All rights reserved.
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.
//
// The CouchFS file system looks roughly like:
// 
// /Volumes/CouchFS/
//                |
//                `-> dbname1/
//                          |
//                          `-> Documents/
//                                      |
//                                      `-> All Documents
//                          |
//                          `-> Design Documents 
//                                             |
//                                             `-> All Design Documents
//                          `-> Views
//                                  |
//                                  `-> All Views as Directories
//                                                            |
//                                                            `->View JSON Return Values
//                          `-> Temporary Views
//                                            |
//                                            `-> Spotlight Queries
//
//                |
//                `-> dbname2/
//                           ...
//                             
//
#import <sys/types.h>
#import <unistd.h>
#import "CouchFS.h"
#import "JSON/JSON.h"

@implementation CouchFS

- (id)initWithRootDB:(NSString *)rootDB {
  if ((self = [super init])) {
    rootDB_ = [rootDB retain];
  }
  return self;
}

- (void) dealloc {
  [rootDB_ release];
  [super dealloc];
}


// -PutRequestToUrl
//
//
- (NSString *) PutRequestToUrl:(NSString *)urlString {
  NSURL *u = [NSURL URLWithString:urlString];
  NSMutableURLRequest *q = [NSURLRequest requestWithURL:u];
  [q setHTTPMethod:@"PUT"];
  NSURLResponse *r = nil;
  NSData *d = nil;
  NSError *e = nil;
  int status;
  d = [NSURLConnection sendSynchronousRequest:q returningResponse:&r error:&e];
  status = [r isKindOfClass:[NSHTTPURLResponse class]] ? [(NSHTTPURLResponse *) r statusCode] : 200;
  if (!d || e || status < 200 || status > 299) {
    return [NSArray arrayWithObject:[@"" lastPathComponent]];
  }
  return [NSString stringWithCString:[d bytes] length:[d length]];
}
// -GetRequestToUrl
//
//
//
- (NSString *) GetRequestToUrl:(NSString *)urlString {
  NSURL *u = [NSURL URLWithString:urlString];
  NSURLRequest *q = [NSURLRequest requestWithURL:u];
  NSURLResponse *r = nil;
  NSData *d = nil;
  NSError *e = nil;
  int status;
  d = [NSURLConnection sendSynchronousRequest:q returningResponse:&r error:&e];
  status = [r isKindOfClass:[NSHTTPURLResponse class]] ? [(NSHTTPURLResponse *) r statusCode] : 200;
  if (!d || e || status < 200 || status > 299) {
    return [NSArray arrayWithObject:[@"" lastPathComponent]];
  }
  return [NSString stringWithCString:[d bytes] length:[d length]];
} 
// -getAllDbs
//
// Returns an NSArray of all top-level folders names.  This is all the CouchDB's
// Databases
//
- (NSArray *)getAllDbs {
  NSString *str = [rootDB_ stringByAppendingString: @"_all_dbs"];
  NSString *s = [self GetRequestToUrl:str];
  return [s JSONValue];
}
// -allDocumentsFromDb
//
//
- (NSArray *)allDocumentsFromDb:(NSString *)db {
  NSString *str = [[rootDB_ stringByAppendingString: db] stringByAppendingString: @"/_all_docs"];
  NSString *s = [self GetRequestToUrl:str];
  NSDictionary *docs = [s JSONValue];
  NSEnumerator *enumerator = [[docs objectForKey:@"rows"] objectEnumerator];
  id row;
  NSArray *docidArray = [NSArray array];
  while (row = [enumerator nextObject]) {
    if ([[row objectForKey:@"id"] hasPrefix:@"_design"] == NO) {
      docidArray = [docidArray arrayByAddingObject:[row objectForKey:@"id"]];
	}
  }
  return docidArray;
}
// -allViewsFromDb
//
//
- (NSArray *)allViewsFromDb:(NSString *)db {
  NSString *str = [[rootDB_ stringByAppendingString: db] stringByAppendingString: @"/_all_docs"];
  NSString *s = [self GetRequestToUrl:str];
  NSDictionary *docs = [s JSONValue];
  NSEnumerator *enumerator = [[docs objectForKey:@"rows"] objectEnumerator];
  id row;
  NSArray *docidArray = [NSArray array];
  while (row = [enumerator nextObject]) {
    if ([[row objectForKey:@"id"] hasPrefix:@"_design"]) {
	  NSString *docsNoExtension = [[row objectForKey:@"id"] lastPathComponent];
      docidArray = [docidArray arrayByAddingObject:docsNoExtension];
	}
  }
  return docidArray;
}
// -getDocumentId
// 
//
- (NSString *)getDocumentId:(NSString *) docId DbName: (NSString *) dbName {
  NSString *str = [rootDB_ stringByAppendingString:dbName];
  str = [[str stringByAppendingString:@"/"] stringByAppendingString:docId];
  return [self GetRequestToUrl:str];
}
// -appendJsonExtension
//
//
- (NSArray *)appendJsonExtension:(NSArray *) docs {
  id row;
  NSEnumerator *enumerator = [docs objectEnumerator];
  NSArray *docidArray = [NSArray array];
  while (row = [enumerator nextObject]) {
	docidArray = [docidArray arrayByAddingObject:[row stringByAppendingString:@".json"]];
  }
  return docidArray;
}
// 
// Fuse Methods To Overide
//
#pragma mark == Overridden GMUserFileSystem Delegate Methods

#pragma mark Directory Contents

- (NSArray *)contentsOfDirectoryAtPath:(NSString *)path error:(NSError **)error {

  if (!path)
    return nil;
	
  NSString *lastComponent = [path lastPathComponent];
  //Show All DB's
  if ([lastComponent isEqualToString:@"/"]) {
    return [self getAllDbs];
  }
  //Second Level Show Documents and Views
  if ([[self getAllDbs] containsObject:lastComponent]) {
    return [NSArray arrayWithObjects:@"Documents", @"Design Documents", @"Views", @"Temporary Views", nil];
  }
  //Thirds Level Document
  if ([lastComponent isEqualToString:@"Documents"]) {
    NSArray *docNoExtension = [self allDocumentsFromDb:[[path pathComponents] objectAtIndex:1]];
	return [self appendJsonExtension: docNoExtension];
  }
  //Third Level Views
  if ([lastComponent isEqualToString:@"Views"]) {
    return [self allViewsFromDb:[[path pathComponents] objectAtIndex:1]];
  }
  //Third Level Design Documents
  if ([lastComponent isEqualToString:@"Design Documents"]) {
    NSArray *viewNoExtention = [self allViewsFromDb:[[path pathComponents] objectAtIndex:1]];
    return [self appendJsonExtension: viewNoExtention];
  }
  //Fourth Level JSON View Query Results
  NSArray *viewNoExtention = [self allViewsFromDb:[[path pathComponents] objectAtIndex:1]];
  if ([viewNoExtention containsObject:lastComponent]) {
    NSString *dbName = [[path pathComponents] objectAtIndex:1];
	NSString *docId = [@"_design/" stringByAppendingString: lastComponent];
	NSDictionary *designDoc = [[self getDocumentId: docId DbName: dbName] JSONValue];
	return [self appendJsonExtension: [[designDoc objectForKey:@"views"] allKeys]];
  }
  return nil;
}

// Files are Readonly
- (NSDictionary *)attributesOfItemAtPath:(NSString *)path error:(NSError **)error {

  if (!path)
    return nil;

  NSMutableDictionary *attr = nil;
  int mode = 0700;
  
  NSString *fileType = NSFileTypeDirectory;
  if ([[path pathExtension] isEqualToString:@"json"]) {
    fileType = NSFileTypeRegular;
	mode = 0400;
  } 

  attr = [NSDictionary dictionaryWithObjectsAndKeys:
    fileType, NSFileType,
    [NSNumber numberWithInt:mode], NSFilePosixPermissions,
	[NSNumber numberWithInt:geteuid()], NSFileOwnerAccountID,
	[NSNumber numberWithInt:getegid()], NSFileGroupOwnerAccountID,
	[NSDate date], NSFileCreationDate,
	[NSDate date], NSFileModificationDate,
	nil];
  return attr;
}

- (NSData *)contentsAtPath:(NSString *)path {
  //If it ends with JSON it has contents
  if ([[path pathExtension] isEqualToString:@"json"]) {
    if ( [[[path lastPathComponent] substringToIndex:2] isEqualToString:@"._"] )
	  return nil;
    NSString *docId = [[path lastPathComponent] stringByDeletingPathExtension];
	if ([[[path pathComponents] objectAtIndex:2] isEqualToString: @"Design Documents"]) {
	  docId = [@"_design/" stringByAppendingString: docId];
	} else if ([[[path pathComponents] objectAtIndex:2] isEqualToString: @"Views"]) {
	  NSString *tmp = [@"_view/" stringByAppendingString: [[path pathComponents] objectAtIndex:3]];
	  docId = [[tmp stringByAppendingString: @"/"] stringByAppendingString: docId];
	}
    NSString *dbName = [[path pathComponents] objectAtIndex:1];
    @try {
	  NSDictionary *jsonDoc = [[self getDocumentId: docId DbName: dbName] JSONValue];
	  NSDictionary *opts = [NSDictionary dictionaryWithObject:@"1" forKey:@"Pretty"];
      return [[jsonDoc JSONRepresentationWithOptions: opts] dataUsingEncoding:NSUTF8StringEncoding];
    }
    @catch (NSException *exception) {
      NSLog(@"CouchFS: Caught %@: %@", [exception name], [exception  reason]);
    }
  }
  return nil;
}


- (BOOL)removeFileAtPath:(NSString *)path handler:(id)handler {
  if (!path)
    return NO;

  NSArray *components = [path pathComponents];
  int ncomp = [components count];
  if (ncomp > 2)
    return NO;

  return YES;
}
- (BOOL)createDirectoryAtPath:(NSString *)path
                   attributes:(NSDictionary *)attributes
                        error:(NSError **)error {
  if (!path)
    return NO;
  // We only allow directories to be created at the very top level
  NSString *dirname = [path stringByDeletingLastPathComponent];
  if ([dirname isEqualToString:@"/"]) {
    //[self PutRequestToUrl: [path lastPathComponent]];
    return YES;
  }
  
  return NO;
}
@end


